﻿using Castle.Core.Logging;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WJP.Dependency;
using WJP.Domain;
using WJP.Domain.Entitys;
using WJP.Events.Bus;

namespace WJP.EntityFramework
{
    public abstract class BaseDbContext : DbContext, ITransientDependency
    {
        public ILogger Logger { get; set; }

        public IEventBus EventBus { get; set; }

        public BaseDbContext()
        {
            InitializeDbContext();
        }

        #region 构造函数

        protected BaseDbContext(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
            InitializeDbContext();
        }

        protected BaseDbContext(DbCompiledModel dbCompiledModel)
            : base(dbCompiledModel)
        {
            InitializeDbContext();
        }

        protected BaseDbContext(DbConnection existingConnection, bool contextOwnsConnection)
            : base(existingConnection, contextOwnsConnection)
        {
            InitializeDbContext();
        }

        protected BaseDbContext(string nameOrConnectionString, DbCompiledModel model)
            : base(nameOrConnectionString, model)
        {
            InitializeDbContext();
        }

        protected BaseDbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext)
            : base(objectContext, dbContextOwnsObjectContext)
        {
            InitializeDbContext();
        }

        protected BaseDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)
            : base(existingConnection, model, contextOwnsConnection)
        {
            InitializeDbContext();
        }

        #endregion

        #region Init

        private void InitializeDbContext()
        {
            SetNullsForInjectedProperties();
            RegisterToChanges();
        }

        private void SetNullsForInjectedProperties()
        {
            Logger = NullLogger.Instance;
            EventBus = NullEventBus.Instance;
        }

        private void RegisterToChanges()
        {
            ((IObjectContextAdapter)this)
                .ObjectContext
                .ObjectStateManager
                .ObjectStateManagerChanged += (s, e) =>
                {
                    var contextAdapter = (IObjectContextAdapter)this;
                    if (e.Action != System.ComponentModel.CollectionChangeAction.Add)
                        return;

                    var entry = contextAdapter.ObjectContext.ObjectStateManager.GetObjectStateEntry(e.Element);
                    switch (entry.State)
                    {
                        case EntityState.Added:
                            CheckAndSetId(entry.Entity);
                            break;
                    }
                };
        }

        protected virtual void CheckAndSetId(object entityAsObj)
        {
            var entity = entityAsObj as IEntity<Guid>;
            if(entity != null && entity.Id == Guid.Empty)
            {
                var entityType = ObjectContext.GetObjectType(entityAsObj.GetType());
                var edmProperty = GetEdmProperty(entityType, nameof(Entity.Id));

                if (edmProperty != null && edmProperty.StoreGeneratedPattern == StoreGeneratedPattern.None)
                {
                    entity.Id = Guid.NewGuid();
                }
            }
        }

        EdmProperty GetEdmProperty(Type type, string propertyName)
        {
            var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;

            var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

            var entityType = metadata.GetItems<EntityType>(DataSpace.OSpace).Single(t => objectItemCollection.GetClrType(t) == type);

            var entitySet = metadata.GetItems<EntityContainer>(DataSpace.SSpace).Single().EntitySets
                .Single(s => s.ElementType.Name == entityType.Name);

            return entitySet.ElementType.Properties.Single(e => string.Equals(e.Name, propertyName, StringComparison.OrdinalIgnoreCase));
        }

        #endregion
    }
}
